/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "RotationEvent.h"
#include <math/utl.h>
#include <xml/Saver.h>

namespace liba
{
	namespace lib3d
	{
		namespace particles
		{
			RotationEvent::RotationEvent() : _time(0), _angle(0), _randomUsed(false), _randomMin(0), _randomMax(0), _relative(true)
			{
			}
			
			RotationEvent::~RotationEvent()
			{
			}

			void RotationEvent::set_rotation(float value)
			{
				_angle = value;
			}

			float RotationEvent::get_rotation()
			{
				return _angle;
			}

			void RotationEvent::set_time(float value)
			{
				_time = value;
			}
			
			RotationEvent::RotationRandom * RotationEvent::get_random_for_particle(Particle * particle)
			{
				if(_randomUsed)
				{
					// try to get random
					RotationRandom * random = static_cast<RotationRandom*>(particle->get_data_for_event(this));
					
					// check if we need setup randomization for this keyframe
					if(random == NULL)
					{
						// setup random rotation
						random         = (RotationRandom*)malloc(sizeof(RotationRandom));
						random->random = math::random(_randomMin, _randomMax);
						random->value  = random->random + _angle;
						
						// save data
						particle->set_data_for_event(this, random, false);
					}
					
					return random;
				}
				
				return NULL;
			}
			
			Float RotationEvent::get_rotation_for_particle(Particle * particle, KeyFrameIterator base_frame, KeyFrameArray * frames)
			{
				// get base angle
				Float angle = _angle;
				
				// random for this frame
				RotationRandom * random = get_random_for_particle(particle);
				
				// if this node relative to parents, search random for parents
				if(random == NULL && _relative && base_frame != frames->begin())
				{
					// search in all parents
					KeyFrameIterator itr = base_frame;
					while(true)
					{
						// try to get random for current frame
						random = itr->get_event<RotationEvent>()->get_random_for_particle(particle);
						if(random != NULL)
						{
							// create random for this keyframe
							RotationRandom * randomCurrent = (RotationRandom*)malloc(sizeof(RotationRandom));
							randomCurrent->random       = random->random;
							randomCurrent->value        = _angle + random->random;
							
							// save data
							particle->set_data_for_event(this, randomCurrent, false);
							
							// reset random pointer
							random = randomCurrent;
							break;
						}
						
						// if we at the end
						if(itr == frames->begin())
							break;
						
						// next frame
						--itr;
					}
				}
				
				// apply random
				if(random != NULL)
					angle = random->value;
				
				// return result angle
				return angle;
			}
			
			void RotationEvent::update_particle(Particle * particle, Float delta_time, KeyFrameIterator base_frame, KeyFrameIterator next_frame, KeyFrameArray * frames)
			{
				// check if we need update base frame angle in particle
				if(particle->baseAngleFrame != base_frame->time)
				{
					particle->baseAngle      = get_rotation_for_particle(particle, base_frame, frames);
					particle->baseAngleFrame = base_frame->time;
				}
				
				// current angle
				Float angle = particle->baseAngle;
				
				// check if we need interpolate with start value
				if(particle->lifeTime < base_frame->time)
				{
					// compute interpolation factor
					Float factor = particle->lifeTime / base_frame->time;
					
					// iterpolate
					angle = math::lerp(particle->startState.angle, angle, factor);
				}
				
				// if this frame interpolates with other
				if(next_frame != frames->end() && next_frame->get_event<RotationEvent>()->_relative)
				{
					// check if we need update next frame alpha in particle
					if(particle->nextAngleFrame != next_frame->time)
					{
						particle->nextAngle      = next_frame->get_event<RotationEvent>()->get_rotation_for_particle(particle, next_frame, frames);
						particle->nextAngleFrame = next_frame->time;
					}
					
					// compute interpolation factor
					Float factor = (particle->lifeTime - base_frame->time) / (next_frame->time - base_frame->time);
					
					// iterpolate
					angle = math::lerp(angle, particle->nextAngle, factor);
				}
				
				// upadte particle rotation
				particle->currentState.angle = angle;
			}
			
			Atom RotationEvent::get_type()
			{
				static Atom type("ROTATION");
				return type;
			}
			
			Float RotationEvent::get_time()
			{
				return _time;
			}
			
			void RotationEvent::on_save_attributes(xml::SaverBase * saver) const
			{
				if(_time > 0.0f)
					saver->save_attribute("Time", _time);

				if(_angle != 0.0f)
					saver->save_attribute("Value", _angle);

				if(!_relative)
					saver->save_attribute("Relative", _relative);

				if(_randomMin != 0.0f)
					saver->save_attribute("RandomMin", _randomMin);

				if(_randomMax != 0.0f)
					saver->save_attribute("RandomMax", _randomMax);
			}

			bool RotationEvent::on_attribute(xml::Provider * provider, const std::string & name, const std::string & value)
			{
				// frame time
				if(name == "Time")
					return converter::convert(value, &_time);
				
				// transparency value
				if(name == "Value")
					return converter::convert(value, &_angle);
				
				// if alpha relative to previous frame
				if(name == "Relative")
					return converter::convert(value, &_relative);
				
				// extract alpha random
				if(name == "RandomMin")
				{
					if(converter::convert(value, &_randomMin))
					{
						_randomUsed = true;
						return true;
					}
					
					return false;
				}
				
				if(name == "RandomMax")
				{
					if(converter::convert(value, &_randomMax))
					{
						_randomUsed = true;
						return true;
					}
					
					return false;
				}
				
				// unknown attribute
				return false;
			}
		}
	}
}